﻿using DotNetCommon.Serialize;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using DotNetCommon.Extensions;

namespace DotNetCommon.Extensions
{
    /// <summary>
    /// 通用对象扩展方法
    /// </summary>
    public static class ObjectExtensions
    {
        #region 通用转换方法
        /// <summary>
        /// 通用转换方法
        /// </summary>
        public static T To<T>(this object value)
        {
            if (value is T) return (T)value;
            Type type = typeof(T);
            if (value == null)
            {
                if (type.IsClass) return default(T);
                if (type.IsNullable()) return default(T);
            }
            return (T)To(value, type);
        }

        /// <summary>
        /// 通用转换方法
        /// </summary>
        /// <param name="value"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static object To(this object value, Type type)
        {
            if (value == null)
            {
                if (type.IsClass) return null;
                if (type.IsNullable()) return null;
            }
            if (value?.GetType() == type) return value;
            if (type == typeof(string))
            {
                object obj = value?.ToString();
                return obj;
            }
            if (type.IsValueType)
            {
                //值类型
                #region 枚举
                if (type.IsEnum)
                {
                    //枚举转换
                    return Enum.Parse(type, value?.ToString());
                }
                #endregion
                #region 字符串转日期
                if (value?.GetType() == typeof(string) && (type == typeof(DateTime) || type == typeof(DateTimeOffset)))
                {
                    if (type == typeof(DateTime))
                    {
                        value = DateTime.Parse(value.ToString());
                        return value;
                    }
                    if (type == typeof(DateTimeOffset))
                    {
                        value = DateTimeOffset.Parse(value.ToString());
                        return value;
                    }
                }
                #endregion
                #region 枚举转数字类型
                if (type.IsNumeric() && value.GetType().IsEnum)
                {
                    var t = (int)value;
                    return value;
                }
                #endregion
                #region DateTimeOffset 转 DateTime
                if (value?.GetType() == typeof(DateTimeOffset) && type == typeof(DateTime))
                {
                    return ((DateTimeOffset)value).DateTime;
                }
                #endregion
                #region 其他
                var converter = TypeDescriptor.GetConverter(type);
                return converter.ConvertFrom(value);
                #endregion
            }
            else
            {
                //引用类型
                return value;
            }
        }

        /// <summary>
        /// 通用转换方法
        /// </summary>
        public static T ToWithCustomFallback<T>(this object value, T defaultValue = default(T))
        {
            try
            {
                return value.To<T>();
            }
            catch
            {
                return defaultValue;
            }
        }
        #endregion

        #region 基于同名属性的对象映射

        /// <summary>
        /// 对象映射，基于同名属性转换原则，使用反射调用<para></para>
        /// 调用方法:
        /// <para></para>
        /// var person=new Person();<para></para>
        /// var dto=person.Mapper&lt;PersonDto>();<para></para>
        /// var persons=new List&lt;Person>();<para></para>
        /// var dtos=persons.Mapper&lt;List&lt;PersonDto>>();<para></para>
        /// </summary>
        /// <typeparam name="TResult">目标类型</typeparam>
        /// <param name="src"></param>
        /// <returns></returns>
        public static TResult Mapper<TResult>(this object src) where TResult : class
        {
            return mapper<TResult>(src);
        }

        private static TResult mapper<TResult>(object src) where TResult : class
        {
            //缓存转换过程，防止循环引用
            var container = new Dictionary<(object src, Type destType), object>();
            var dest = (TResult)Activator.CreateInstance(typeof(TResult));
            container.Add((src, typeof(TResult)), dest);
            return (TResult)mapper(src, dest, typeof(TResult), container);
        }

        private static object mapper(object src, object dest, Type destType, Dictionary<(object src, Type destType), object> container)
        {
            //null值的转换
            if (src == null)
            {
                return null;
            }
            var srcType = src.GetType();
            if (srcType == destType)
            {
                return src;
            }
            #region 集合转换
            //集合转换
            if (CollectNames.Any(p => srcType.FullName.StartsWith(p)) && CollectNames.Any(p => destType.FullName.StartsWith(p)))
            {
                var geneType = destType.GenericTypeArguments[0];
                destType = typeof(List<>).MakeGenericType(geneType);
                if ((int)src.GetType().GetProperties().FirstOrDefault(prop => prop.Name == "Count").GetValue(src) == 0)
                {
                    //是集合,但元素个数为0
                    var list = Activator.CreateInstance(destType);
                    return list;
                }
                else
                {
                    //是集合,且有数据
                    var list = Activator.CreateInstance(destType);
                    //循环转换
                    var count = (int)src.GetType().GetProperty("Count").GetValue(src);
                    var indexProp = src.GetType().GetProperty("Item");
                    for (var i = 0; i < count; i++)
                    {
                        var src2 = indexProp.GetValue(src, new object[] { i });
                        object ele = null;
                        var cons = geneType.GetConstructors().FirstOrDefault(con => con.GetParameters().Length == 0);
                        if (cons != null)
                        {
                            ele = cons.Invoke(new object[0]);
                        }
                        container.Add((src2, geneType), ele);
                        var newValue = mapper(src2, ele, geneType, container);
                        destType.GetMethod("Add").Invoke(list, new object[] { newValue });
                    }
                    return list;
                }
            }
            //数组转换 和集合转换逻辑一致,代码稍有不同
            if (destType.IsArray && srcType.IsArray)
            {
                var geneType = destType.GetElementType();
                destType = typeof(List<>).MakeGenericType(geneType);
                if ((int)src.GetType().GetProperties().FirstOrDefault(prop => prop.Name == "Length").GetValue(src) == 0)
                {
                    //是数组,但元素个数为0
                    var list = Activator.CreateInstance(destType);
                    return destType.GetMethod("ToArray").Invoke(list, new object[0]);
                }
                else
                {
                    //是集合,且有数据
                    var list = Activator.CreateInstance(destType);
                    //循环转换
                    var indexProp = src.GetType().GetProperty("Item");
                    foreach (var src2 in src as Array)
                    {
                        object ele = null;
                        var cons = geneType.GetConstructors().FirstOrDefault(con => con.GetParameters().Length == 0);
                        if (cons != null)
                        {
                            ele = cons.Invoke(new object[0]);
                        }
                        container.Add((src2, geneType), ele);
                        var newValue = mapper(src2, ele, geneType, container);
                        destType.GetMethod("Add").Invoke(list, new object[] { newValue });
                    }
                    return destType.GetMethod("ToArray").Invoke(list, new object[0]);
                }
            }
            #endregion
            #region 非集合转换
            var props = src.GetType().GetProperties();
            var props2 = destType.GetProperties();
            foreach (var prop in props)
            {
                var destProp = props2.FirstOrDefault(p => p.Name == prop.Name);
                if (destProp != null)
                {
                    //过滤没有set方法的属性
                    if (destProp.GetSetMethod() == null) continue;
                    var val = prop.GetValue(src);
                    if (val == null)
                    {
                        destProp.SetValue(dest, null);
                        continue;
                    }
                    if (prop.PropertyType.IsValueType || prop.PropertyType == destProp.PropertyType)
                    {
                        //值类型或类型相同直接转
                        destProp.SetValue(dest, val);
                        continue;
                    }
                    //引用类型,递归转
                    object newValue = null;
                    var key = (val, destProp.PropertyType);
                    if (container.ContainsKey(key))
                    {
                        newValue = container[key];
                    }
                    else
                    {
                        object ele = null;
                        var cons = destProp.PropertyType.GetConstructors().FirstOrDefault(con => con.GetParameters().Length == 0);
                        if (cons != null)
                        {
                            ele = cons.Invoke(new object[0]);
                        }
                        container.Add((val, destProp.PropertyType), ele);
                        newValue = mapper(val, ele, destProp.PropertyType, container);
                    }
                    destProp.SetValue(dest, newValue);
                }
            }
            #endregion
            return dest;
        }
        #endregion

        #region Modify 递归修改指定名称的属性值
        /// <summary>
        /// 根据属性名递归修改属性值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <param name="propName">属性名</param>
        /// <param name="modifyAction">根据旧值,修改后返回新值</param>
        /// <returns></returns>
        public static T Modify<T>(this T model, string propName, Func<object, object> modifyAction)
        {
            if (model == null) return model;
            _filterDeal(model);
            return model;

            void _filterDeal(object model)
            {
                if (model == null) return;
                var type = model.GetType();
                //这里不接受简单类型
                if (type.IsSimpleType()) return;
                //集合
                if (CollectNames.Any(p => type.FullName.StartsWith(p)))
                {
                    var count = (int)type.GetProperties().FirstOrDefault(prop => prop.Name == "Count").GetValue(model);
                    if (count == 0)
                    {
                        //元素个数为0
                        return;
                    }
                    else
                    {
                        //循环转换
                        var indexProp = type.GetProperty("Item");
                        for (var i = 0; i < count; i++)
                        {
                            var src2 = indexProp.GetValue(model, new object[] { i });
                            _filterDeal(src2);
                        }
                    }
                }
                else if (type.IsArray)
                {
                    //数组
                    var count = (int)type.GetProperties().FirstOrDefault(prop => prop.Name == "Length").GetValue(model);
                    if (count == 0)
                    {
                        //元素个数为0
                        return;
                    }
                    else
                    {
                        //是集合,且有数据
                        var indexProp = type.GetProperty("Item");
                        foreach (var src2 in model as Array)
                        {
                            _filterDeal(src2);
                        }
                    }
                }
                else
                {
                    //dto对象
                    var props = model.GetType().GetProperties();
                    foreach (var prop in props)
                    {
                        if (prop.Name == propName && prop.CanWrite)
                        {
                            var old = prop.GetValue(model);
                            try
                            {
                                var newValue = modifyAction(old);
                                prop.SetValue(model, newValue);
                            }
                            catch { }
                        }
                        else
                        {
                            _filterDeal(prop.GetValue(model));
                        }
                    }
                }
            }
        }
        #endregion

        #region 使用dto对象改变实体对象上的值
        /// <summary>
        /// 将指定dto对象的属性值投影到当前对象（entity）上，改变entity的属性值，基于同名属性反射<para></para>
        /// 注意：这个方法是为根据dto更新entity提供的，调用此方法时，无论是entity还是dto都不应该是集合，执行主体逻辑如下：
        /// <list type="bullet">
        /// <item>如果dto为null，直接返回entity；</item>
        /// <item>如果dto的类型和entity的类型相同，则直接返回dto对象；</item>
        /// <item>如果entity为null，则新创建一个entity实例，修改后返回新创建的实例；</item>
        /// <item>属性先进行比较，相等则不改变；</item>
        /// <item>对于集合类型，直接覆盖；</item>
        /// <item>对于引用类型，递归修改；</item>
        /// </list>
        /// </summary>
        /// <typeparam name="TResult">目标类型</typeparam>
        /// <param name="dto">dto对象，将这个对象的属性映射到当前对象上，改变当前对象的属性值</param>
        /// <param name="baseObj">基础对象,不能为null</param>
        public static TResult ModifyByDto<TResult>(this TResult baseObj, object dto) where TResult : class
        {
            if (dto == null) return baseObj;
            return mapperModify<TResult>(dto, baseObj);
        }

        private static TResult mapperModify<TResult>(object src, TResult baseObj) where TResult : class
        {
            //缓存转换过程，防止循环引用
            var container = new Dictionary<(object src, Type destType), object>();
            TResult dest = baseObj;
            if (dest == null)
            {
                dest = (TResult)Activator.CreateInstance(typeof(TResult));
            }
            container.Add((src, typeof(TResult)), dest);
            return (TResult)mapperModify(src, dest, typeof(TResult), container);
        }

        private static object mapperModify(object src, object dest, Type destType, Dictionary<(object src, Type destType), object> container)
        {
            //null值的转换
            if (src == null)
            {
                return null;
            }
            var srcType = src.GetType();
            if (srcType == destType)
            {
                return src;
            }
            #region 集合转换
            //集合转换
            if (CollectNames.Any(p => srcType.FullName.StartsWith(p)) && CollectNames.Any(p => destType.FullName.StartsWith(p)))
            {
                var geneType = destType.GenericTypeArguments[0];
                destType = typeof(List<>).MakeGenericType(geneType);
                if ((int)src.GetType().GetProperties().FirstOrDefault(prop => prop.Name == "Count").GetValue(src) == 0)
                {
                    //是集合,但元素个数为0
                    var list = Activator.CreateInstance(destType);
                    return list;
                }
                else
                {
                    //是集合,且有数据
                    var list = Activator.CreateInstance(destType);
                    //循环转换
                    var count = (int)src.GetType().GetProperty("Count").GetValue(src);
                    var indexProp = src.GetType().GetProperty("Item");
                    for (var i = 0; i < count; i++)
                    {
                        var src2 = indexProp.GetValue(src, new object[] { i });
                        object ele = null;
                        var cons = geneType.GetConstructors().FirstOrDefault(con => con.GetParameters().Length == 0);
                        if (cons != null)
                        {
                            ele = cons.Invoke(new object[0]);
                        }
                        container.Add((src2, geneType), ele);
                        var newValue = mapper(src2, ele, geneType, container);
                        destType.GetMethod("Add").Invoke(list, new object[] { newValue });
                    }
                    return list;
                }
            }
            //数组转换 和集合转换逻辑一致,代码稍有不同
            if (destType.IsArray && srcType.IsArray)
            {
                var geneType = destType.GetElementType();
                destType = typeof(List<>).MakeGenericType(geneType);
                if ((int)src.GetType().GetProperties().FirstOrDefault(prop => prop.Name == "Length").GetValue(src) == 0)
                {
                    //是数组,但元素个数为0
                    var list = Activator.CreateInstance(destType);
                    return destType.GetMethod("ToArray").Invoke(list, new object[0]);
                }
                else
                {
                    //是集合,且有数据
                    var list = Activator.CreateInstance(destType);
                    //循环转换
                    var indexProp = src.GetType().GetProperty("Item");
                    foreach (var src2 in src as Array)
                    {
                        object ele = null;
                        var cons = geneType.GetConstructors().FirstOrDefault(con => con.GetParameters().Length == 0);
                        if (cons != null)
                        {
                            ele = cons.Invoke(new object[0]);
                        }
                        container.Add((src2, geneType), ele);
                        var newValue = mapper(src2, ele, geneType, container);
                        destType.GetMethod("Add").Invoke(list, new object[] { newValue });
                    }
                    return destType.GetMethod("ToArray").Invoke(list, new object[0]);
                }
            }
            #endregion
            #region 非集合转换
            var props = src.GetType().GetProperties();
            var props2 = destType.GetProperties();
            foreach (var prop in props)
            {
                var destProp = props2.FirstOrDefault(p => p.Name == prop.Name);
                if (destProp != null)
                {
                    //过滤没有set方法的属性
                    if (destProp.GetSetMethod() == null) continue;
                    var val = prop.GetValue(src);
                    var destVal = destProp.GetValue(dest);
                    //相等,无需改变值
                    if (prop.PropertyType == destProp.PropertyType)
                    {
                        try
                        {
                            if (val == null && destProp == null) continue;
                            if (val != null && val.Equals(destVal)) continue;
                        }
                        catch { }
                    }

                    if (val == null)
                    {
                        destProp.SetValue(dest, null);
                        continue;
                    }
                    if (prop.PropertyType.IsValueType || prop.PropertyType == destProp.PropertyType)
                    {
                        //值类型或类型相同直接转
                        destProp.SetValue(dest, val);
                        continue;
                    }
                    //引用类型,递归转
                    object newValue = null;
                    var key = (val, destProp.PropertyType);
                    if (container.ContainsKey(key))
                    {
                        newValue = container[key];
                    }
                    else
                    {
                        object ele = destVal;
                        if (ele == null)
                        {
                            var cons = destProp.PropertyType.GetConstructors().FirstOrDefault(con => con.GetParameters().Length == 0);
                            if (cons != null)
                            {
                                ele = cons.Invoke(new object[0]);
                            }
                        }
                        container.Add((val, destProp.PropertyType), ele);
                        newValue = mapperModify(val, ele, destProp.PropertyType, container);
                    }
                    //相等的直接跳过
                    if (newValue == val) continue;
                    destProp.SetValue(dest, newValue);
                }
            }
            #endregion
            return dest;
        }
        #endregion

        private static List<string> CollectNames = new List<string>()
        {
            "System.Collections.Generic.List`1",
            "System.Collections.Generic.IList`1",
            "System.Collections.Generic.IEnumerable`1",
            "System.Collections.Generic.ICollection`1"
        };

        #region ToJson & ToJsonFast
        /// <summary>
        /// 序列化为json字符串
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="settings">序列化设置对象</param>
        /// <returns></returns>
        public static string ToJson(this object obj, JsonSerializerSettings settings = null)
        {
            if (obj == null) return null;
            return Newtonsoft.Json.JsonConvert.SerializeObject(obj, settings);
        }

        /// <summary>
        /// 指定常用的设置,序列化为json字符串
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="dateFormatString">日期时间格式</param>
        /// <param name="isLongToString">是否将long型转为字符串</param>
        /// <param name="IgnoreNull">是否忽略null值的属性</param>
        /// <param name="enum2String">是否将枚举转换为字符串</param>
        /// <param name="lowerCamelCase">属性名称的首字母是否小写</param>
        /// <param name="isIntend">是否格式缩进</param>
        /// <param name="isAllToString">是否将所有数据类型转换为字符串</param>
        /// <param name="allNumDigit">设定的所有数字的最大小数位数(默认为null，即: 不限制)</param>
        /// <param name="decimalDigit">仅针对decimal设定的最大小数位数(默认为null，即: 不限制)</param>
        /// <param name="otherSettings">其他的设置</param>
        /// <returns></returns>
        public static string ToJsonFast(this object obj, string dateFormatString = "yyyy-MM-dd HH:mm:ss", bool IgnoreNull = false, bool enum2String = true, bool lowerCamelCase = false, bool isIntend = false, bool isLongToString = false, bool isAllToString = false, int? allNumDigit = null, int? decimalDigit = null, Action<JsonSerializerSettings> otherSettings = null)
        {
            var settings = new Newtonsoft.Json.JsonSerializerSettings()
            {
                DateFormatString = dateFormatString,
                Converters = new List<JsonConverter>()
            };
            if (IgnoreNull)
            {
                settings.NullValueHandling = NullValueHandling.Ignore;
            }
            if (enum2String)
            {
                settings.Converters.Add(new StringEnumConverter());
            }
            if (lowerCamelCase)
            {
                settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            }
            if (isIntend)
            {
                settings.Formatting = Formatting.Indented;
            }
            settings.Converters.Add(new PrimitiveConvertor(isLongToString, isAllToString, allNumDigit, decimalDigit));
            otherSettings?.Invoke(settings);
            return JsonConvert.SerializeObject(obj, settings);
        }
        #endregion

        /// <summary>
        /// 是否是数字类型
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static bool IsNumeric(this object obj)
        {
            if (obj == null) return false;
            return obj.GetType().IsNumeric();
        }

        /// <summary>
        /// 是否是枚举类型
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static bool IsEnum(this object obj)
        {
            if (obj == null) return false;
            return obj.GetType().IsEnum;
        }
    }
}
